#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <libxml/parser.h>
#include <libxml/HTMLparser.h>
#include "sheet.h"
#include <trim.h>
#include <directory.h>

#define TMP_HTML "incannualdiv.html"

typedef struct Node {
    char date[DATE_LEN];
    long revenue;
    long items[ITEM_NUM];
    struct Node *next;
} Node;

typedef struct {
    int num;
    struct Node *node;
} Rows;

static char **sheet_items;

void selectSheet(const char *file);

Rows *procSheetItems(xmlDocPtr xmldoc, xmlNodePtr xmlnode, Rows *list);

Rows *procSheetInit(xmlDocPtr xmldoc, xmlNodePtr xmlnode);

int updateCSV(Rows *list);

long procNumStr(char *numstr);

void printRows(Rows *list);

/*
 * Get the first ELEMENT Child Node..
 * Skips over COMMENT and (Excess) TEXT Nodes
 */
static xmlNodePtr getFirstChildElementNode(xmlNodePtr cur);

/*
 * Get the next ELEMENT Node.
 * Skips over COMMENT and (Excess) TEXT Nodes
 */
static xmlNodePtr getNextElementNode(xmlNodePtr cur);

/*
 * Return the first TEXT Child Node string
 */
static char *getFirstChildTextNodeString(xmlDocPtr doc, xmlNodePtr cur, bool trim);
/*
 * Gets the first TEXT Child Node..
 * Skips over COMMENT nodes and ELEMENT Nodes
 * The XML schema unsures the next node is not
 * an element node.
 */
static xmlNodePtr getFirstChildTextNode(xmlNodePtr cur);

static Rows 
*parse_html(const char *file)
{
    xmlDocPtr  doc;
    xmlNodePtr cur_node;
    Rows *list = NULL;

    doc = (xmlDocPtr)htmlParseFile(file, NULL);

    /*
     * htmlParseFile will wrap the input file with
     * <html>
     *   <body>
     *      input file
     *   </body>
     * </html>
     */

    if (doc == NULL) {
        fprintf(stderr, "Document not parsed successfully.\n");
        return NULL;
    }

    // Now is <html>
    cur_node = xmlDocGetRootElement(doc);
    if (cur_node == NULL) {
        fprintf(stderr, "Empty document\n");
        return NULL;
    }

    // Now is <body>
    cur_node = getFirstChildElementNode(cur_node);

    // Now  is <div id="incannualdiv">
    cur_node = getFirstChildElementNode(cur_node);

    cur_node = getFirstChildElementNode(cur_node);
    for ( ; cur_node != NULL; cur_node = getNextElementNode(cur_node)) {
        if (xmlStrcmp(cur_node->name, (xmlChar *)"table") == 0) {
            cur_node = getFirstChildElementNode(cur_node);
            for ( ; cur_node != NULL;
                    cur_node = getNextElementNode(cur_node)) {
                if (xmlStrcmp(cur_node->name, (xmlChar *)"thead") == 0) {
                    list = procSheetInit(doc, cur_node);
                } else if (xmlStrcmp(cur_node->name, (xmlChar *)"tbody") == 0) {
                    if (list == NULL) {
                        fprintf(stderr, "Possible html structure change\n");
                        return NULL;
                    }
                    procSheetItems(doc, cur_node, list);
                    break;
                }
            }
            break;
        }
    }

    return list;
}

static char *home;
static char *exchange;
static char *symbol;

int
main(int argc, char **argv)
{
    Rows *list = NULL;
    exchange = argv[1];
    symbol   = argv[2];
    char html_file[64];

    home = getenv("HOME");
    snprintf(html_file, sizeof(html_file),
        "%s/%s/%s/%s/incannualdiv.html", home, html_dir, exchange, symbol);
    selectSheet(html_file);

    if ((list = parse_html(html_file)) == NULL) {
        printf("fail to parse %s file\n", html_file);
    } else {
        printRows(list);
        updateCSV(list);
    }

    return 0;
}

void
selectSheet(const char *file)
{
    if (strstr(file, "inc") != NULL) {
        sheet_items = income_items;
    }

    return;
}

Rows
*procSheetItems(xmlDocPtr xmldoc, xmlNodePtr xmlnode, Rows *list)
{
    xmlNodePtr cur_node;
    xmlNodePtr sub_node;
    char       *title;
    char       *text;
    Node       *node;
    int        i = 0, j;

    // Now is <tr>
    cur_node = getFirstChildElementNode(xmlnode);
    for ( ; cur_node != NULL; cur_node = getNextElementNode(cur_node)) {
        // Now is <td>
        sub_node = getFirstChildElementNode(cur_node);
        if (sub_node != NULL) {
            title = getFirstChildTextNodeString(xmldoc, sub_node, true);
            if (sheet_items[i] != NULL && strstr(title, sheet_items[i]) != NULL) {
                sub_node = getNextElementNode(sub_node);
                if (list != NULL && list->num != 0) {
                    node = list->node;
                    for (j = 0; j < list->num; j++) {
                        text = getFirstChildTextNodeString(xmldoc, sub_node, true);
                        node->items[i] = procNumStr(text);
                        node = node->next;
                        xmlFree(text);
                        sub_node = getNextElementNode(sub_node);
                    }
                }
                i++;
            }
        } else {
            fprintf(stderr, "No date info available\n");
        }
    }

    return list;
}

Rows
*procSheetInit(xmlDocPtr xmldoc, xmlNodePtr xmlnode)
{
    xmlNodePtr sub_node;
    char *text, *sub_text;
    Node *node;
    Rows *list = NULL;

    // Now is <tr>
    sub_node = getFirstChildElementNode(xmlnode);
    sub_node = getFirstChildElementNode(sub_node);
    if (sub_node != NULL) {
        list = (Rows *)calloc(1, sizeof(Rows));
        for ( ; sub_node != NULL;
            sub_node = getNextElementNode(sub_node)) {
            text = getFirstChildTextNodeString(xmldoc, sub_node, true);
            if ((sub_text = strstr(text, m_end)) != NULL) {
                if (list->node == NULL) {
                    list->node = (Node *)calloc(1, sizeof(Node));
                    node = list->node;
                    strncpy(node->date, sub_text + strlen(m_end), sizeof(node->date));
                } else {
                    node->next = (Node *)calloc(1, sizeof(Node));
                    node = node->next;
                    strncpy(node->date, sub_text + strlen(m_end), sizeof(node->date));
                }
                list->num++;
            }
            xmlFree(text);
        }
    } else {
        fprintf(stderr, "No date info available\n");
        return NULL;
    }

    return list;
}

int
updateCSV(Rows *list)
{
    Node *node = NULL;
    Rows *tmp = list;
#define FILE_SIZE 128
    char file[FILE_SIZE];
    char file_tmp[FILE_SIZE];
    FILE *fp;
    int i, j;

    snprintf(file, sizeof(file),
        "%s/%s/%s_%s.csv", home, csv_dir, exchange, symbol);

    snprintf(file_tmp, sizeof(file_tmp), "%s.tmp", file);

    if (list == NULL) {
        return -1;
    }

    fp = fopen(file, "w");

    node = tmp->node;
    for (i = 0; i < tmp->num; i++ ) {
        fprintf(fp, "%s,", node->date);
        for (j = 0; j < ITEM_NUM; j++) {
            fprintf(fp, "%ld,", node->items[j]);
        }
        fprintf(fp, "\n");
        node = node->next;
    }
    fclose(fp);
    return 0;
}

long
procNumStr(char *numstr)
{
    char copy[32];
    char *p = numstr;
    int i = 0;

    while (*p != '\0') {
        if ((*p >= '0' && *p <= '9') || (*p == '-')) {
            copy[i++] = *p;
        } else if (*p == '.') {
            copy[i] = '\0';
            return (strtol(copy, NULL, 10));
        }
        p++;
    }

    return 0;
}

void
printRows(Rows *list)
{
    Node *node = NULL;
    Rows *tmp = list;
    int i, j;

    if (list == NULL) {
        return;
    }

    node = tmp->node;
    printf("%d\n", tmp->num);
    for (i = 0; i < tmp->num; i++ ) {
        printf("%s, ", node->date);
        for (j = 0; j < ITEM_NUM; j++) {
            printf("%ld, ", node->items[j]);
        }
        node = node->next;
        printf("\n");
    }
}

static xmlNodePtr
getFirstChildElementNode(xmlNodePtr cur) {

    cur = cur->xmlChildrenNode;
    while (cur) {

        if (cur->type == XML_ELEMENT_NODE) {
            return (cur);
        }
        cur = cur->next;
    }

    return (cur);
}

static xmlNodePtr
getNextElementNode(xmlNodePtr cur) {

    cur = cur->next;
    while (cur) {

        if (cur->type == XML_ELEMENT_NODE) {
            return (cur);
        }
        cur = cur->next;
    }
    
    return (cur); 
}

static char
*getFirstChildTextNodeString(xmlDocPtr doc, xmlNodePtr cur, bool trim)
{
    xmlNodePtr child;
    char *text = NULL;

    child = getFirstChildTextNode(cur);
    if (child) {
        text = (char *)xmlNodeListGetString(doc, child, 1);
        if (trim == true) {
            ltrim(text, '\n');
            rtrim(text, '\n');
        }
    }

    return text;
}

static xmlNodePtr
getFirstChildTextNode(xmlNodePtr cur) {

    cur = cur->xmlChildrenNode;
    while (cur) {

        if (cur->type == XML_TEXT_NODE) {
            return (cur);
        }
        cur = cur->next;
    }

    return (cur);
}
